'''
Created on 20/03/2009

@author: eh01
'''
from xml.dom.minidom import parseString
from yarra.datastore.dst import DST
from yarra.datastore import dst, dstfactory

class DSO(object):
    '''
    classdocs
    '''

    uri = None
    type = None
    
    __opening_tag = "<"
    __closing_tag = "</"
    __end_tag = ">"
    __dso_tag_name = "dso" 
    __dst_tag_name = "dst"
    
    def __init__(self, id=None, created_timestamp=None, version=None, active=True, xml_string=None, dsts=None):
        self.__id = id
        self.__created_timestamp = created_timestamp
        self.__version = version
        self.__active = active
        if dsts:
            # convert to set. If it is already a set, then the function doesn't change anything
            self.__dsts = set(dsts)
        else:
            self.__dsts = set([])
        if xml_string:
            xml = parseString(xml_string)
            dstElements = xml.getElementsByTagName(self.__dst_tag_name)
            for dstElement in dstElements:
                self.__dsts.add(DST(xml_string = dstElement.toxml()))

    def get_dsts(self, subject=None, predicate=None):
        if not subject and not predicate:
            return [dst for dst in self.__dsts if not dst.remove]
        elif subject and not predicate:
            return [dst for dst in self.__dsts if dst.subject == subject and not dst.remove]
        else:
            return [dst for dst in self.__dsts if dst.subject == subject and dst.predicate == predicate and not dst.remove]
    
    def add_dsts(self, dsts):
        # temp list of dsts that are to be added if nothing goes wrong
        updated_dsts = set([])
        # temp list of dsts to be removed from dso if nothing goes wrong
        removed_dsts = set([])
        
        for dst in dsts:
            if dst.id and not self._does_dst_belong_to_dso(dst):
                raise DSOUpdateException("DST with id {0} does not belong to DSO".format(dst.id))
            elif dst.id:
                # remove dst with that id
                dst = self._get_dst(dst.id)
                removed_dsts.add(dst)
                # create a new version of the dst
                updated_dst = dstfactory.copy_dst(dst)
                updated_dsts.add(updated_dst)               
            else:
                # add the new dst to the list
                updated_dsts.add(dst)
    
        # add all the updated dsts if no exceptions raised
        self.__dsts.update(updated_dsts)
        
        # remove all dsts if no exceptions raised
        for dst in removed_dsts:
            dst.remove = True

    def remove_dsts(self, dsts=None):
        dsts_to_be_removed = set([])
        for dst in dsts:
            if self._does_dst_belong_to_dso(dst):
                dsts_to_be_removed.add(dst)
            else:
                raise DSOUpdateException("DST with id {0} does not belong to DSO".format(dst.id))
        
        for dst in dsts_to_be_removed:
            dst.remove = True

    def get_id(self):
        return self.__id
        
    def get_created_timestamp(self):
        return self.__created_timestamp

    def set_created_timestamp(self, value):
        self.__created_timestamp = value

    def get_version(self):
        return self.__version

    def get_active(self):
        return self.__active

    def to_xml(self):
        xml = self.__opening_tag + self.__dso_tag_name + self.__end_tag
        for dst in self.dsts:
            xml += dst.to_xml()
        xml += self.__closing_tag + self.__dso_tag_name + self.__end_tag
        return xml

    def get_new_dsts(self):
        return [dst for dst in self.dsts if not dst.id]
    
    def is_dsts_updated(self):
        if not self.is_new() and self.get_new_dsts():
            return True
        else:
            return False
    
    def is_new(self):
        if not self.id:
            return True
        else:
            return False

    def get_next_anonymous_reference(self):
        return self._get_max_anonymous_reference() + 1
    
    def _get_max_anonymous_reference(self):
        max_anonymous_reference = 0
        for dst in self.dsts:
            if isinstance(dst.object, int):
                if not max_anonymous_reference:
                    max_anonymous_reference = dst.object
                elif max_anonymous_reference < dst.object:
                    max_anonymous_reference = dst.object
        return max_anonymous_reference            
            
    id = property(get_id, None, None, None)
    dsts = property(get_dsts, None, None, None)
    version = property(get_version, None, None, None)
    created_timestamp = property(get_created_timestamp, set_created_timestamp, None, None)
    active = property(get_active, None, None, None)
    
    def _does_dst_belong_to_dso(self, dst):
        if dst.id:            
            for existing_dst in self.__dsts:
                if dst.id == existing_dst.id:
                    return True
        return False
    
    def _get_dst(self, id):
        for dst in self.__dsts:
            if dst.id == id:
                return dst
        return None
    
    def __cmp__(self, other):
        if self.id == other.id and \
            self.dsts == other.dsts and \
            self.version == other.version and \
            self.created_timestamp == other.created_timestamp and \
            self.active == other.active and \
            self.uri == other.uri and \
            self.type == other.type:
            return 0
        else:
            return -1
        
class DSOUpdateException(Exception):
    pass